package com.ebanca.drawme;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceView;

import com.ebanca.drawme.model.Connection;
import com.ebanca.drawme.model.Point;

/**
 * Class that offers the drawing of the map.
 */
public class MapView extends SurfaceView implements ViewMenuDirectionNavigation {

	/**
	 * Constants.
	 */
	private static final int POINTER_OFFSET_X = 3;
	private static final int POINTER_OFFSET_Y = 3;

	private static final int UNSCALED_DEVICE_SIZE_X = 12;
	private static final int UNSCALED_DEVICE_SIZE_Y = 12;

	private static final int TOOLTIP_OFFSET_X = 10;
	private static final int TOOLTIP_OFFSET_Y = 10;

	private static final int TOOLTIP_MARGIN_X = 1;
	private static final int TOOLTIP_MARGIN_Y = 1;

	private static final int UNSCALED_LEGEND_SIZE_X = 30;
	private static final int UNSCALED_LEGEND_SIZE_Y = 17;

	private static final int LEGEND_MARGIN_X = 3;
	private static final int LEGEND_MARGIN_Y = 3;

	/**
	 * Variables.
	 */
	public Bitmap image = null;
	public Bitmap streetH = null;
	public Bitmap streetV = null;

	/**
	 * The next variables are defined in config file
	 */
	// Floor variables
	private String[] levelLabels = null;

	// Devices variables

	private int marginX = 0;
	private int marginY = 0;
	private int currentSelectedId = -1;
	private int currentSelectedLevel = 0;
	private int currentPosX = 0;
	private int currentPosY = 0;
	private boolean legendMode = false;

	private int deviceSizeX = 0;
	private int deviceSizeY = 0;
	private int legendSizeX = 0;
	private int legendSizeY = 0;
	private double scale = 1;

	private Paint paint;

	private int tempX = 0;
	private int tempY = 0;
	private Point startPoint;

	private List<Connection> connectionList = new ArrayList<Connection>();
	private XmlConfiguration xmlConfiguration;

	public MapView(Context context, double scale) {
		super(context);
		this.scale = scale;
		this.setBackgroundColor(getResources().getColor(R.color.backgroundColor));

		initializeConfigurables();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		initializeImage(canvas);
	}

	public void init() {
		// Store rescaled image for performance reasons
		image = rescaleImage(image);

		marginX = streetV.getWidth();
		marginY = streetH.getHeight();

		deviceSizeX = (int) (UNSCALED_DEVICE_SIZE_X * scale);
		deviceSizeY = (int) (UNSCALED_DEVICE_SIZE_Y * scale);
		legendSizeX = (int) (UNSCALED_LEGEND_SIZE_X * scale);
		legendSizeY = (int) (UNSCALED_LEGEND_SIZE_Y * scale);

	}

	/**
	 * Draws map and sensors based on current settings.
	 */
	private void initializeImage(Canvas canvas) {

		boolean getCanvas = false;
		if (canvas == null) {
			canvas = this.getHolder().lockCanvas();
			getCanvas = true;
		}

		paint = new Paint();

		canvas.drawBitmap(image, marginX, marginY, null);

		addLegend(canvas);

		for (Point point : xmlConfiguration.getDeviceCoords()) {
			addSeverityToImage(canvas, (int) (point.getX() * scale) + marginX, (int) (point.getY() * scale) + marginY);
		}

		paint.setStrokeWidth(5);
		paint.setColor(getResources().getColor(android.R.color.holo_blue_dark));
		for (Connection con : connectionList) {
			canvas.drawLine(con.getStart().getX(), con.getStart().getY(), con.getEnd().getX(), con.getEnd().getY(),
					paint);
		}

		if (getCanvas) {
			this.getHolder().unlockCanvasAndPost(canvas);
			this.refreshDrawableState();
		}
	}

	/**
	 * Draws a sensor at position (x,y).
	 */
	private void addSeverityToImage(Canvas canvas, int x, int y) {

		Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.event_warning);
		canvas.drawBitmap(rescaleImage(icon), x, y, null);

	}

	/**
	 * Method for handling a click on a screen point.
	 * 
	 * Checks if a sensor or a legend item was clicked and provide specific
	 * handler.
	 */
	protected void pointerPressed(int x, int y) {

		Point pointedDevice = getPointedDeviceId(x, y);
		if (pointedDevice != null) {
			legendMode = false;
			startPoint = pointedDevice;
		} else {
			int tempLevel = getPointedLevel(x, y);
			if (tempLevel != -1) {
				legendPointerDragged(x, y, tempLevel);
			}
		}
	}

	protected void pointerReleased(int x, int y) {

		Point pointedDevice = getPointedDeviceId(x, y);
		if (pointedDevice != null) {
			legendMode = false;
			Point endPoint = pointedDevice;
			drawConnection(startPoint, endPoint);
		} else {
			int tempLevel = getPointedLevel(x, y);
			if (tempLevel != -1) {
				legendPointerDragged(x, y, tempLevel);
			}
		}
	}

	/**
	 * Handler for clicking on a legend item.
	 */
	private void legendPointerDragged(int x, int y, int selectedLevel) {
		currentPosX = x;
		currentPosY = y;

		legendMode = true;
		currentSelectedId = -1;
		currentSelectedLevel = selectedLevel;
		initializeImage(null);
		addLegendPointer(null, currentPosX, currentPosY);
		this.invalidate();
	}

	/**
	 * Handler for navigation through sensors, using phone keyboard and also
	 * application touch menu.
	 */
	private boolean onKeyDownF(int keyCode) {
//		int nextId = currentSelectedId;
//		if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
//			nextId = getNextId(true, false);
//		} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
//			nextId = getNextId(true, true);
//		} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
//			nextId = getNextId(false, false);
//		} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
//			nextId = getNextId(false, true);
//		} else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
//			pointerPressed(currentPosX, currentPosY);
//			return true;
//		} else {
//			return true; // NOTE: This avoids repaint
//		}
//		if (!legendMode) {
//			final int forLoops = deviceIds.length;
//			for (int i = 0; i < forLoops; i++) {
//				if (deviceIds[i] == nextId) {
//					sensorPointerDragged(i);
//					break;
//				}
//			}
//		} else {
//			currentPosX = image.getWidth() + legendSizeX + marginX - LEGEND_MARGIN_X;
//			currentPosY = image.getHeight() - (currentSelectedLevel + 1) * legendSizeY + marginY;
//			pointerPressed(currentPosX, currentPosY);
//		}
		return true;
	}

	/**
	 * Draws the legend control.
	 */
	private void addLegend(Canvas canvas) {
		final int forLoops = levelLabels.length;
		for (int level = 0; level < forLoops; level++) {
			if (level == currentSelectedLevel) {
				paint.setColor(getResources().getColor(R.color.legendSelectionBorderColor));
			} else {
				paint.setColor(getResources().getColor(R.color.backgroundColor));
			}
			paint.setStyle(Paint.Style.STROKE);

			tempX = image.getWidth() + marginX + streetV.getWidth();
			tempY = image.getHeight() - (level + 1) * legendSizeY + marginY;
			canvas.drawRect(tempX, tempY, tempX + legendSizeX - 1, tempY + legendSizeY - 1, paint);

			if (level == currentSelectedLevel) {
				paint.setColor(getResources().getColor(R.color.legendSelectionBackgroundColor));
			} else {
				paint.setColor(getResources().getColor(R.color.backgroundColor));
			}
			paint.setStyle(Paint.Style.FILL);
			canvas.drawRect(tempX + 1, tempY + 1, tempX + legendSizeX - 2, tempY + legendSizeY - 2, paint);

			paint.setColor(Color.BLACK);

			canvas.drawText(levelLabels[level], image.getWidth() + marginX + streetV.getWidth() + LEGEND_MARGIN_X,
					tempY + LEGEND_MARGIN_Y + 17, paint);

		}

	}


	/**
	 * Draws a pointer when selecting a legend item.
	 */
	private void addLegendPointer(Canvas canvas, int x, int y) {
		boolean getCanvas = false;
		if (canvas == null) {
			canvas = this.getHolder().lockCanvas();
			getCanvas = true;
		}
		Bitmap pointer = null;
		pointer = BitmapFactory.decodeResource(getResources(), R.drawable.pointer);
		canvas.drawBitmap(pointer, x + POINTER_OFFSET_X, y + POINTER_OFFSET_Y, null);
		if (getCanvas) {
			this.getHolder().unlockCanvasAndPost(canvas);
		}
	}

	/**
	 * Draws a tooltip at point(x,y) containing 'text'.
	 */
	private void addToolTip(Canvas canvas, int x, int y, String text, boolean armed) {
		paint.setTextSize(10);

		float tooltipSizeX = paint.measureText(text) + 2 * TOOLTIP_MARGIN_X;
		int tooltipSizeY = 15;
		Bitmap armedIcon = null;
		try {
			if (armed) {
				armedIcon = BitmapFactory.decodeResource(getResources(), R.drawable.indicator_armed);
			}
		} catch (Exception ex) {
			// Do nothing
		}
		if (armedIcon != null) {
			tooltipSizeX += armedIcon.getWidth() + TOOLTIP_MARGIN_X;
		}
		paint.setStyle(Paint.Style.STROKE);
		tempX = x + TOOLTIP_OFFSET_X;
		tempY = y + TOOLTIP_OFFSET_Y;
		canvas.drawRect(tempX, tempY, tempX + tooltipSizeX, tempY + tooltipSizeY, paint);

		paint.setStyle(Paint.Style.FILL);
		paint.setColor(Color.WHITE);
		canvas.drawRect(tempX + 1, tempY + 1, tempX + tooltipSizeX - 1, tempY + tooltipSizeY - 1, paint);
		paint.setColor(Color.BLACK);
		if (armedIcon != null) {
			canvas.drawBitmap(armedIcon, x + TOOLTIP_OFFSET_X + TOOLTIP_MARGIN_X, y + TOOLTIP_OFFSET_Y
					+ TOOLTIP_MARGIN_Y, null);
			canvas.drawText(text, x + armedIcon.getWidth() + TOOLTIP_OFFSET_X + TOOLTIP_MARGIN_X * 2, y
					+ TOOLTIP_OFFSET_Y + TOOLTIP_MARGIN_Y + paint.getTextSize(), paint);
		} else {
			canvas.drawText(text, x + TOOLTIP_OFFSET_X + TOOLTIP_MARGIN_X, y + TOOLTIP_OFFSET_Y + TOOLTIP_MARGIN_Y
					+ paint.getTextSize(), paint);
		}

	}

	/**
	 * Add a pointer for selected legend item.
	 */
	private void addLevelSelectionPointer(Canvas canvas, int level) {

		Bitmap selectionPointer = BitmapFactory.decodeResource(getResources(), R.drawable.selection_pointer);

		tempY = image.getHeight() - (level + 1) * legendSizeY + marginY;

		canvas.drawBitmap(selectionPointer, image.getWidth() + marginX + streetV.getWidth() + LEGEND_MARGIN_X, tempY
				+ LEGEND_MARGIN_Y + 20, null);
	}


	/**
	 * Return the sensor ID at given coordinates or '-1' if there is no sensor.
	 */
	private Point getPointedDeviceId(int x, int y) {
		for (Point point: xmlConfiguration.getDeviceCoords()) {
			boolean insideRangeX = (x >= (int) (point.getX() * scale) + marginX && x <= (int) (point.getX() * scale)
					+ marginX + deviceSizeX);
			boolean insideRangeY = (y >= (int) (point.getY() * scale) + marginY && y <= (int) (point.getY() * scale)
					+ marginY + deviceSizeY);
			if (insideRangeX && insideRangeY) {
				return point;
			}
		}
		return null;
	}

	/**
	 * Returns the floor level for the legend item at point(x,y).
	 */
	private int getPointedLevel(int x, int y) {
		for (int level = 0; level < levelLabels.length; level++) {
			boolean insideRangeX = (x >= image.getWidth() + marginX && x <= image.getWidth() + marginX + legendSizeX);
			boolean insideRangeY = (y >= image.getHeight() + marginY - (level + 1) * legendSizeY && y <= image
					.getHeight() + marginY - (level + 1) * legendSizeY + legendSizeY);
			if (insideRangeX && insideRangeY) {
				return level;
			}
		}
		return -1;

	}

	private void drawConnection(Point start, Point end) {
		connectionList.add(new Connection(start, end));
		this.invalidate();
	}

	private Bitmap rescaleImage(Bitmap image) {
		int sourceWidth = image.getWidth();
		int sourceHeight = image.getHeight();
		int targetWidth = (int) (sourceWidth * scale);
		int targetHeight = (int) (sourceHeight * scale);
		Bitmap resizedBitmap = Bitmap.createScaledBitmap(image, targetWidth, targetHeight, true);

		return resizedBitmap;
	}

	private void initializeConfigurables() {
		this.xmlConfiguration = XmlHandler.getInstance().getXmlConfiguration(this.getContext()).get(0);

	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			pointerPressed((int) event.getX(), (int) event.getY());
			return true;
		} else if (event.getAction() == MotionEvent.ACTION_UP) {
			pointerReleased((int) event.getX(), (int) event.getY());
			return true;
		}
		return false;
	}

	/**
	 * Implementation of the methods from ViewMenuDirectionNavigation interface.
	 */

	@Override
	public void onDown() {
		onKeyDownF(KeyEvent.KEYCODE_DPAD_DOWN);
	}

	@Override
	public void onLeft() {
		onKeyDownF(KeyEvent.KEYCODE_DPAD_LEFT);
	}

	@Override
	public void onRight() {
		onKeyDownF(KeyEvent.KEYCODE_DPAD_RIGHT);

	}

	@Override
	public void onUp() {
		onKeyDownF(KeyEvent.KEYCODE_DPAD_UP);

	}

	@Override
	public void onEnter() {

	}
}
